package sceneGraph;

import java.util.ArrayList;
import java.util.List;

import javax.vecmath.*;

public abstract class AbstractPlanet extends AbstractSceneObject implements ISceneObject {
	private boolean isSun;
	private static int sunCounter = 0;
	private int sunNumber = 0;

	private float radius = 0;
	private Point3f pos;
	protected Point3f absPos;

	private Vector3f orbitAxis;
	private float orbitSpeed;
	private float angle;

	private List<Moon> moons;
	private List<AbstractPlanetObject> planetObjects;

	private Player player;
	private int playerHeight = 0;

	private PlanetMesh mesh;

	private String textureFilePath;
	private String heightMapFilePath;

	@Override
	public abstract void accept(Visitor visitor);

	public AbstractPlanet(float radius, Point3f pos, Color3f kDiffuse, Color3f kSpecular, Color3f kAmbient, float nShineness, boolean isSun,
			Vector3f orbitAxis, float orbitSpeed, String textureFilePath, String heightMapFilePath) {
		super(kDiffuse, kSpecular, kAmbient, nShineness);
		this.radius = radius;
		this.pos = new Point3f(pos);
		this.isSun = isSun;
		this.orbitAxis = new Vector3f(orbitAxis);
		this.orbitSpeed = orbitSpeed;
		this.angle = 0;
		this.moons = new ArrayList<Moon>();
		this.planetObjects = new ArrayList<AbstractPlanetObject>();
		this.player = null;
		this.textureFilePath = textureFilePath;
		this.heightMapFilePath = heightMapFilePath;
		this.mesh = new PlanetMesh(radius, 0, textureFilePath, heightMapFilePath);
		this.absPos = new Point3f(this.pos);

		if (isSun) {
			sunNumber = sunCounter++;
			if (sunNumber > 7) {
				System.err.println("Cannot assign more than 8 suns");
				sunNumber = 7;
			}
		}
	}

	public int getSunNumber() {
		return sunNumber;
	}

	public float getRadius() {
		return radius;
	}

	public Point3f getPosition() {
		return pos;
	}

	public Point3f getAbsPosition() {
		return absPos;
	}

	public boolean isSun() {
		return isSun;
	}

	public Vector3f getOrbitAxis() {
		return orbitAxis;
	}

	public void incrementAngle() {
		this.angle = angle + orbitSpeed;
	}

	public float getAngle() {
		return angle;
	}

	public void addMoon(Moon moon) {
		moons.add(moon);
	}

	public List<Moon> getMoons() {
		return moons;
	}

	public Player getPlayer() {
		return player;
	}

	public PlanetMesh getMesh() {
		return mesh;
	}

	private Point3f sphereToHeightMapPoint(Point3f p) {
		Vector3f posVector = new Vector3f(p);
		posVector.scale(2.0f * mesh.getHeightAtPoint(p));
		return new Point3f(posVector);
	}

	private Point3f getPositionOnPlanet(Point3f objectPos, float objectRadius) {
		// Set position to fit planet's heightmap
		Vector3f initPosVector = new Vector3f(objectPos);
		initPosVector.normalize();
		initPosVector.scale(radius);
		Point3f initPos = sphereToHeightMapPoint(new Point3f(initPosVector));

		// Add object radius to position
		Vector3f objRadiusVector = new Vector3f(initPos);
		objRadiusVector.normalize();
		objRadiusVector.scale(objectRadius);
		initPos.add(objRadiusVector);

		return new Point3f(initPos);
	}

	private void positionObject(AbstractPlanetObject planetObj) {
		Point3f initPos = getPositionOnPlanet(planetObj.getPosition(), planetObj.getRadius());

		Vector3f initLeftDirection = planetObj.getLeftDirection();

		Vector3f initUpDirection = new Vector3f(initPos);
		initUpDirection.normalize();

		planetObj.setVectors(initPos, initUpDirection, initLeftDirection);
	}

	public void setPlayer(Player player) {
		this.player = player;

		// Place the player somewhere on the planet
		player.setPosition(new Point3f(0f, 0f, 1f));
		positionObject(player);

		player.setInitDistance(radius);
	}

	public void setPlayerPosition(Point3f estPos) {
		// Fix player estimated position to be on the sphere
		Vector3f tmpSphereVector = new Vector3f(estPos);
		tmpSphereVector.normalize();
		tmpSphereVector.scale(radius);
		Point3f spherePos = new Point3f(tmpSphereVector);

		// Get the final position of the player (including the terrain)
		Point3f finalPos = getPositionOnPlanet(spherePos, player.getRadius());

		// Don't let object to get to high places
		Point3f playerSpherePos = getPositionOnPlanet(player.getPosition(), player.getRadius());
		if (playerSpherePos.distance(finalPos) > 0.5f && playerHeight == 0) {
			return;
		}
		finalPos.scaleAdd(0.02f * playerHeight, finalPos);

		// Calculate the up direction at the given newPos
		Vector3f newUpDirection = new Vector3f(player.getPosition());
		newUpDirection.normalize();

		// Update player position & up vector
		player.moveTo(finalPos, newUpDirection);
	}

	public void setPlayerHeight(int playerHeight) {
		this.playerHeight = Math.abs(playerHeight);
	}

	public void removePlayer() {
		this.player = null;
	}

	public AbstractPlanetObject getIntersectedObject() {
		for (AbstractPlanetObject p : planetObjects) {
			if (p.intersects(player.getPosition(), player.getRadius() + playerHeight * 0.1f)) {
				return p;
			}
		}
		return null;
	}

	public void addPlanetObject(AbstractPlanetObject planetObject) {
		positionObject(planetObject);
		planetObjects.add(planetObject);
	}

	public void removePlanetObject(AbstractPlanetObject planetObject) {
		planetObjects.remove(planetObject);
	}

	public void repositionPlanetObjects() {
		for (AbstractPlanetObject obj : planetObjects) {
			positionObject(obj);
		}
	}

	public List<AbstractPlanetObject> getPlanetObjects() {
		return planetObjects;
	}

	abstract public void recalcAbsPosition();

	public void reMesh(int subDevSize) {
		this.mesh = new PlanetMesh(radius, subDevSize, textureFilePath, heightMapFilePath);

		for (Moon m : getMoons()) {
			m.reMesh(Math.max(1, subDevSize));
		}
	}
}
